/*  Copyright 2003  Auri Marcelo Rizzo Vicenzi, Marcio Eduardo Delamaro, 			    Jose Carlos Maldonado

    This file is part of Jabuti.

    Jabuti is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as 
    published by the Free Software Foundation, either version 3 of the      
    License, or (at your option) any later version.

    Jabuti is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Jabuti.  If not, see <http://www.gnu.org/licenses/>.
*/


package br.jabuti.mobility;


import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;

import java.io.*;
import java.util.*;

import br.jabuti.instrumenter.*;
import br.jabuti.lookup.*;
import br.jabuti.project.JabutiProject;

import br.jabuti.util.*;


/**

 This is the class that implements the functionality of a
 JVM code instrumenter. Using such object it is possible
 to insert JVM code in a given JVM method.

 @version: 0.00001
 @author: M�rcio Eduardo Delamaro

 */

public class HostProberInstrum {
    public static void usage() {
        System.out.println(ToolConstants.toolName + " v. " + ToolConstants.toolVersion);
        System.out.println("\nHostProberLoader usage:");
        System.out.println("-------------------\n");
        System.out.println("java br.jabuti.mobility.HostProberLoader [-CP <CLASSPATH>] [-D <DIR>] -P <PROJECT_NAME> <BASE_CLASS> [<PARAMETERS>]\n");
        System.out.println("      [-d <DIR>]              Optional parameter. Specify the directory where the project");
        System.out.println("                              is located. If not specified, the current directory is assumed.");
        System.out.println("      -p <PROJECT_FILE>       Specify the name of the project to be used. The");
        System.out.println("                              project must be a valid project file (.jbt) generated by");
        System.out.println("                              instrument the base class.");
        System.out.println("      -name <PROJECT_NAME>    Specifies the name of the project. This name will be ");
        System.out.println("                              used to identify the project to the MuServer");
        System.out.println("      -h <hostaddress>        Specifies the address of the server that will receive the test case agent");
        System.out.println("                              The format is hostname:port");
        System.out.println("      -delay <delaytime>      Specifies the amount of time to wait between agents to the test case server");
		System.out.println("      -o <dirname>            Specifies the name of the directory where to place instrumented files"); 
        System.out.println("      <BASE_CLASS>            The class file to be executed.");
        System.out.println("                              executed, it have to be specified.");
        System.out.println("\nCopyright (c) 2002\n");
    }

    public static void main(String args[]) throws Throwable {
//        String classPath = null;
    	br.jabuti.util.Persistency.init();
        String workDir = null;
        String projName = null;
        String baseClass = null; // The class file to be executed
        String outName = null;
        String[] parameters = null; // The paramenters used to execute the main file
						
        JabutiProject project = null;
        Program program = null; // The program to be tested
        HashSet toInstrumenter = null, agentClasses = new HashSet();
        String projFileName = null; // A given class or compressed file name
        String hostName = "localhost:1988";
        int delay = 2000;

        if (args.length > 0) {

            int i = 0;
			
            for (i = 0; i < args.length - 1;i++) 
            {
                // -CP: Class path
                if (("-d".equals(args[i])) && (i < args.length - 1)) {
                    i++;
                    workDir = args[i];
                } // -P: project name
                else if (("-p".equals(args[i])) && (i < args.length - 1)) {
                    i++;
                    projFileName = args[i];
                }
                else if (("-name".equals(args[i])) && (i < args.length - 1)) {
                    i++;
                    projName = args[i];
                }
                else if (("-h".equals(args[i])) && (i < args.length - 1)) {
                    i++;
                    hostName = args[i];
                } 
				else if (("-o".equals(args[i])) && (i < args.length - 1)) {
					i++;
					outName = args[i];
				} 
                else if (("-delay".equals(args[i])) && (i < args.length - 1)) {
                    i++;
                    delay = Integer.parseInt(args[i]);
                } 
                else
                	break;
            }

            if (i >= args.length) {
                System.out.println("Error: Missing base class!!!");
                usage();
                System.exit(0);
            }
            // Ultimo par�metro... file seguido dos par�metros
            baseClass = args[i++];
            
						
            parameters = new String[args.length - i];
            for (int j = 0; i < args.length; j++, i++) {
                parameters[j] = args[i];
            }

            // Checking if all essential parameters are not null
            if ((projFileName == null) || (outName == null) ||
                 (projName == null) ) {
                System.out.println("Error: Missing parameter!!!");
                usage();
                System.exit(0);
            }
			
            // Creating the absolute path to a given project
            String absoluteName = null;

            if (workDir != null) {
                absoluteName = workDir + File.separator + projFileName;
            } else {
                absoluteName = projFileName;
            }

				
            try {
                File theFile = new File(absoluteName);

                if (!theFile.isFile()) // verifica se existe
                {
                    System.out.println("File " + theFile.getName() + " not found");
                    System.exit(0);
                }
	          	
                project = JabutiProject.reloadProj( theFile.toString(), false );
                program = project.getProgram();
				
                toInstrumenter = project.getInstr();
            } catch (Exception e) {
                ToolConstants.reportException(e, ToolConstants.STDERR);
                System.exit(0);
            }
            
            // pega quais sao as classe que herdam da classe mucode.abstractions.MuAgent
			
			String[] agClassVet = program.getSubClassClosure
			                                 (HostProbeInsert.MUCODE_AGENT);
			
			for (int j = 0; j < agClassVet.length; j++)
			{
				agentClasses.add(agClassVet[j]);
			} 
							                  
            HostProbeInsert dpi = new HostProbeInsert(program, 
            										toInstrumenter,
            										agentClasses);
            Map mp = null;

            try {
                mp = dpi.instrument( project.getCFGOption() );
                
				JavaClass jv = (JavaClass) mp.get(baseClass);
				if ( jv == null )
				{
					System.out.println("Base class not found in project: " + baseClass);
					System.exit(0);
				}
				jv = instrumentMain(jv, hostName, projName, delay);
				mp.put(baseClass, jv);

                // substitui os objetos JavaClass por byte[]
       			Iterator it0 = mp.keySet().iterator();
       			File outFile = new File(outName);
       			
      			while (it0.hasNext())
      			{
      				String clName = (String) it0.next();
      				jv = (JavaClass) mp.get(clName);
      				File fdir = new File(outFile, jv.getPackageName().replace('.','/'));
      				fdir.mkdirs();
      				int k = clName.lastIndexOf('.');
      				k = (k < 0 ? 0 : k+1);
      				clName = clName.substring(k, clName.length());
      				File f = new File(fdir, clName+".class");
      				jv.dump(f);
      			}
			} catch (Exception eu) {
				System.err.println(eu);
				eu.printStackTrace();
			}

        } else {
            usage();
        }
    }

	/**
	 *  Instrumenta a classe base, methodo Main
	 */
	private static JavaClass instrumentMain(JavaClass jv, String hostName, String projName, int delay) 
	            throws Exception {
		ClassGen cg = new ClassGen(jv);				 
		ConstantPoolGen cp = cg.getConstantPool();
  		
		Method[] methods = cg.getMethods();
		int i = 0;
		for (i=0; i < methods.length; i++)
			if (methods[i].getName().equals("main")
				&& methods[i].getSignature().equals("([Ljava/lang/String;)V") 
				)
			{
				break;
			}
		if ( i >= methods.length)
		{
			System.out.println("Method static public main(String[]) not found");
			System.exit(0);
		}
		   	   
		MethodGen mg = new MethodGen(methods[i], 
				cg.getClassName(),
				cp);
                        
		InstructionList il = mg.getInstructionList();
		InstructionHandle last = il.getStart();
//		InstructionHandle pen = last.getPrev(); 
//		il.delete(il.getStart(), pen);
		ASMInstrumenter gi = new ASMInstrumenter(mg, cg, cp);
		gi.insertBefore(last, 
				" ldc \"" + hostName + "\"" + // empilha o endereco do servidor de teste
				" ldc \"" + projName + "\"" + // empilha o nome projeto
				" ldc " + delay  + // empilha o tempo de espera
				"aconst_null " +
				"invokestatic br.jabuti.mobility.mobile.HostProber" + 
				" init \"(Ljava/lang/String;Ljava/lang/String;ILmucode/MuServer;)V\"");
		methods[i] = mg.getMethod();
		cg.setConstantPool(cp);
		cg.setMethods(methods);
		return (cg.getJavaClass());

	}
}
